package org.baicaixiaozhan.learncommon.config;

import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.ReadMode;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.List;

/**
 * Redisson 相关配置
 *
 * @since 2021/6/30
 * @author baicaixiaozhan
 */
@Slf4j
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.database}")
    private int database;
    @Value("${spring.redis.host:#{null}}")
    private String host;
    @Value("${spring.redis.port:#{null}}")
    private String port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;


    @Value("${spring.redis.sentinel.master:#{null}}")
    private String sentinelMaster;
    @Value("${spring.redis.sentinel.nodes:#{null}}")
    private String sentinelNode;

    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    public RedissonConfig(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * RedissonClient,单机模式
     */
    @ConditionalOnProperty(name = "spring.redis.host")
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() {
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setAddress("redis://" + host + ":" + port);
        singleServerConfig.setTimeout(timeout);
        singleServerConfig.setDatabase(database);
        if (!Strings.isNullOrEmpty(password)) {
            singleServerConfig.setPassword(password);
        }
        return Redisson.create(config);
    }

    @Bean("resetRateLimiter")
    public RedisScript<Boolean> resetRateLimiter() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setLocation(new ClassPathResource("/lua/resetRateLimiter.lua"));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }

    @Bean("accessWithIPLimiter")
    public RedisScript<Boolean> redisScript() {
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setLocation(new ClassPathResource("/lua/SlidingWindowAccessLimiter.lua"));
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }

    /**
     * redisson 版本令牌桶实现
     */
    @Bean("defaultRedissonRateLimiter")
    public RRateLimiter defaultRedissonRateLimiter(RedissonClient redissonClient) {
        // 预先删除 redis 中存在的数据
        Boolean isDel = stringRedisTemplate.execute(resetRateLimiter(),
                List.of("redissonRateLimiter"), "defaultRedissonRateLimiter");
        log.info("is delete : {}", isDel);

        RRateLimiter rateLimiter = redissonClient.getRateLimiter("defaultRedissonRateLimiter");
        // 每秒钟生产 50 个令牌
        rateLimiter.trySetRate(RateType.OVERALL, 50, 1, RateIntervalUnit.SECONDS);
        return rateLimiter;
    }

    /**
     * 哨兵模式 redisson 客户端
     */
    @Bean(destroyMethod = "shutdown")
    @ConditionalOnProperty(name = "spring.redis.sentinel.master")
    public RedissonClient redissonSentinel() {
        log.info("sentinel redisProperties:哨兵模式");
        Config config = new Config();

        List<String> nodes = Splitter.on(",")
                .omitEmptyStrings()
                .trimResults()
                .splitToList(sentinelNode);
        List<String> newNodes = Lists.newArrayListWithCapacity(nodes.size());
        nodes.forEach(index -> newNodes.add(index.startsWith("redis://") ? index : "redis://" + index));

        SentinelServersConfig serverConfig = config.useSentinelServers()
                .addSentinelAddress(newNodes.toArray(new String[0]))
                .setMasterName(sentinelMaster)
                .setReadMode(ReadMode.SLAVE)
                .setTimeout(timeout)
                .setMasterConnectionPoolSize(100)
                .setSlaveConnectionPoolSize(100)
                .setCheckSentinelsList(false);

        if (!Strings.isNullOrEmpty(password)) {
            serverConfig.setPassword(password);
        }
        return Redisson.create(config);
    }

}
